package com.share.device.service.impl;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.share.common.core.context.SecurityContextHolder;
import com.share.common.core.domain.R;
import com.share.common.core.exception.ServiceException;
import com.share.common.security.utils.SecurityUtils;
import com.share.device.domain.*;
import com.share.device.emqx.EmqxClientWrapper;
import com.share.device.emqx.ProtocolConvertUtil;
import com.share.device.emqx.constant.EmqxConstants;
import com.share.device.emqx.handler.impl.PowerBankUnlockHandler;
import com.share.device.service.*;
import com.share.order.api.RemoteOrderInfoService;
import com.share.order.api.domain.OrderInfo;
import com.share.rule.api.RemoteFeeRuleService;
import com.share.rule.api.domain.FeeRule;
import com.share.user.api.RemoteUserService;
import com.share.user.api.domain.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author YC
 */
@Slf4j
@Service
public class DeviceServiceImpl implements IDeviceService {

    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private IStationService stationService;
    @Autowired
    private ICabinetService cabinetService;
    @Autowired
    private IMapService mapService;
    @Autowired
    private RemoteFeeRuleService remoteFeeRuleService;
    @Autowired
    private RemoteUserService remoteUserService;
    @Autowired
    private RemoteOrderInfoService remoteOrderInfoService;
    @Autowired
    private EmqxClientWrapper emqxClientWrapper;
    @Autowired
    private PowerBankUnlockHandler powerBankUnlockHandler;
    @Autowired
    private ICabinetSlotService cabinetSlotService;
    @Autowired
    private IPowerBankService powerBankService;


    /**
     * 业务逻辑：
     * 经纬度信息存储在mongodb中，使用mongodb的geo查询功能，查询附近站点（半径画圆范围）
     * mongodb中只存放了部分信息，在从数据库中查询出其他信息，在内存中封装为最终数据结果返回
     */
    @Override
    public List<StationVo> nearbyStation(String longitude, String latitude) {
        // 创建中心点（当前位置）的经纬度
        GeoJsonPoint geoJsonPoint = new GeoJsonPoint(Double.parseDouble(longitude), Double.parseDouble(latitude));
        // 创建查询半径
        Distance distance = new Distance(5, Metrics.KILOMETERS);
        // 创建圆
        Circle circle = new Circle(geoJsonPoint, distance);
        // 封装查询条件并查询封装到StationLocation
        Query query = Query.query(Criteria.where("location").withinSphere(circle));
        List<StationLocation> list = mongoTemplate.find(query, StationLocation.class);
        // 从list获取所有站点id
        List<Long> stationIdList = list.stream().map(StationLocation::getStationId).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(stationIdList)) {
            return new ArrayList<>();
        }
        // 根据所有站点id获取对应站点数据
        List<Station> stationList = stationService.list(
                new LambdaQueryWrapper<Station>()
                        .in(Station::getId, stationIdList)
        );
        // 封装最终数据
        List<StationVo> stationVoList = new ArrayList<>();
        stationList.forEach(station -> {
            StationVo stationVo = getStation(
                    station.getId(),
                    station.getLongitude().toString(),
                    station.getLatitude().toString()
            );
            stationVoList.add(stationVo);
        });
        return stationVoList;
    }

    @Override
    public StationVo getStation(Long id, String latitude, String longitude) {
        // 根据站点ID获取详情
        Station station = stationService.getById(id);
        // 站点详情 封装到StationVo
        StationVo stationVo = new StationVo();
        BeanUtils.copyProperties(station, stationVo);
        // 计算距离（腾讯地图提供API）封装到StationVo
        Double distance = mapService.calculateDistance(
                longitude, latitude,
                station.getLongitude().toString(),
                station.getLatitude().toString()
        );
        stationVo.setDistance(distance);
        // 获取柜机信息
        Cabinet cabinet = cabinetService.getById(station.getCabinetId());
        // 可用充电宝数量大于0，可借用 封装到StationVo
        if (cabinet.getAvailableNum() > 0) {
            stationVo.setIsUsable("1");
        } else {
            stationVo.setIsUsable("0");
        }
        // 获取空闲插槽数量大于0，可归还 封装到StationVo
        if (cabinet.getFreeSlots() > 0) {
            stationVo.setIsReturn("1");
        } else {
            stationVo.setIsReturn("0");
        }
        // 根据费用规则ID查询站点的费用规则详细（远程调用）封装到StationVo
        FeeRule feeRule = remoteFeeRuleService.detailFeeRule(station.getFeeRuleId()).getData();
        stationVo.setFeeRule(feeRule.getDescription());
        //返回数据
        return stationVo;
    }

    /**
     * 业务逻辑
     * 1. 当前登录用户是否支持免押金
     * 2. 当前用户是否有未完成订单或未支付订单
     * 3. 系统查找可用充电宝信息
     * 4. 用户选择后MQTT协议弹出充电宝
     */
    @Override
    public ScanChargeVo scanCharge(String cabinetNo) {
        // 获取当前登录用户id
        Long loginUserId = SecurityContextHolder.getUserId();
        // 根据当前登录用户id查询用户信息（远程调用），从用户信息获取是否支持免押金充电
        UserInfo userInfo = remoteUserService.getInfo(loginUserId).getData();
        if (userInfo == null) {
            throw new ServiceException("获取用户信息失败");
        }
        // 判断是否免押金
        if ("0".equals(userInfo.getDepositStatus())) {
            throw new ServiceException("未申请免押金使用");
        }
        ScanChargeVo scanChargeVo = new ScanChargeVo();
        // 判断用户是否有未完成订单（远程调用），数据库设计：订单状态 0未完成 1未支付 2已完成
        OrderInfo orderInfo = remoteOrderInfoService.getNoFinishOrder(loginUserId).getData();
        if (orderInfo != null) {
            String status = orderInfo.getStatus();
            if ("0".equals(status)) {
                // 返回信息设计：1：成功 2：有未归还充电宝 3：有未支付订单
                scanChargeVo.setStatus("2");
                scanChargeVo.setMessage("有未归还充电宝，请归还后使用");
                return scanChargeVo;
            }
            if ("1".equals(status)) {
                scanChargeVo.setStatus("3");
                scanChargeVo.setMessage("有未支付订单，去支付");
                return scanChargeVo;
            }
        }
        // 从柜机里面获取最优充电宝（抽取方法：checkAvailablePowerBank）
        AvailablePowerBankVo availablePowerBankVo = this.checkAvailablePowerBank(cabinetNo);
        // 验证最优充电宝是否存在
        if (availablePowerBankVo.getPowerBankNo() == null) {
            throw new ServiceException("无可用充电宝");
        }
        if (!StringUtils.isEmpty(availablePowerBankVo.getErrMessage())) {
            throw new ServiceException(availablePowerBankVo.getErrMessage());
        }
        // 使用MQTT协议 把选择最优充电宝弹出（MQTT封装方法：publish）
        JSONObject object = new JSONObject();
        object.put("uId", SecurityContextHolder.getUserId());
        object.put("mNo", "mm" + RandomUtil.randomString(8));
        object.put("cNo", cabinetNo);
        object.put("pNo", availablePowerBankVo.getPowerBankNo());
        object.put("sNo", availablePowerBankVo.getSlotNo());
        String topic = String.format(EmqxConstants.TOPIC_SCAN_SUBMIT, cabinetNo);
        String message = ProtocolConvertUtil.convertString(object);
        emqxClientWrapper.publish(topic, message);
//        try {
//            Thread.sleep(2000);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }
        // 返回扫码充电返回对象
        scanChargeVo.setStatus("1");
        return scanChargeVo;
    }

    /**
     * 系统获取可用最优充电宝
     * 业务逻辑：
     * 1. 根据cabinetNo查询cabinetBean，判断cabinetBean中的availableNum字段可用充电宝数量
     * 2. 根据cabinetBean的ID查询插槽列表，从插槽列表中获取对应的可用充电宝ID
     * 3. 根据充电宝ID获取对应详细信息，按照电量排序，获取最优的充电宝
     * 4. 获取最优充电宝的插槽信息，锁定插槽（更新插槽状态），封装后返回这个最优充电宝的信息
     *
     * @param cabinetNo 柜机编号
     * @return AvailablePowerBankVo
     */
    private AvailablePowerBankVo checkAvailablePowerBank(String cabinetNo) {
        // 创建AvailablePowerBankVo对象
        AvailablePowerBankVo availablePowerBankVo = new AvailablePowerBankVo();
        // 根据cabinetNo柜机编号查询柜机信息
        Cabinet cabinet = cabinetService.getBtCabinetNo(cabinetNo);
        // 判断柜机可用充电宝数量是否大于0
        Integer availableNum = cabinet.getAvailableNum();
        if (availableNum == 0) {
            availablePowerBankVo.setErrMessage("无可用充电宝");
            return availablePowerBankVo;
        }
        // 根据柜机id查询插槽列表，返回list集合
        List<CabinetSlot> cabinetSlotList = cabinetSlotService.list(
                new LambdaQueryWrapper<CabinetSlot>()
                        .eq(CabinetSlot::getCabinetId, cabinet.getId())
                        .eq(CabinetSlot::getStatus, "1")
        );
        // 从插槽列表list集合中获取对应充电宝id集合（去除null）
        List<Long> powerBankIdList = cabinetSlotList.stream()
                .map(CabinetSlot::getPowerBankId)
                .filter(Objects::nonNull).collect(Collectors.toList());
        // 根据充电宝id列表查询对应充电宝信息
        List<PowerBank> powerBankList = powerBankService.list(
                new LambdaQueryWrapper<PowerBank>()
                        .in(PowerBank::getId, powerBankIdList)
                        .eq(PowerBank::getStatus, "1")
        );
        // 验证集合不为空
        if (CollectionUtils.isEmpty(powerBankList)) {
            availablePowerBankVo.setErrMessage("无可用充电宝");
            return availablePowerBankVo;
        }
        // 充电宝信息集合电量降序排序，获取第一个最优的充电宝
        if (powerBankList.size() > 1) {
            Collections.sort(powerBankList,
                    (o1, o2) -> o2.getElectricity().compareTo(o1.getElectricity()));
        }
        PowerBank powerBank = powerBankList.get(0);
        // 获取电量最多的充电宝对应插槽信息
        CabinetSlot cabinetSlot = cabinetSlotList.stream()
                .filter(item -> null != item.getPowerBankId()
                        && item.getPowerBankId().equals(powerBank.getId()))
                .collect(Collectors.toList()).get(0);
        // 锁定插槽（更新插槽状态）
        cabinetSlot.setStatus("2");
        cabinetSlotService.updateById(cabinetSlot);
        // 返回需要vo数据
        availablePowerBankVo.setPowerBankNo(powerBank.getPowerBankNo());
        availablePowerBankVo.setSlotNo(cabinetSlot.getSlotNo());
        return availablePowerBankVo;
    }

}
